package common.parser;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;


/**
 * This class reads J2ME classes. The parsing process is organized through
 * subclassing, with this base class only parsing the constant pool and
 * otherwise entirely ignoring the information read from the stream. Especially,
 * there are not provided any method code related member attributes and methods.
 * The classes in this hierarchy also store the data retrieved from reading the
 * file and provide access methods. Their modification is not intended. The
 * framework currently does not consider visibility, versions, and interface
 * declarations.
 * 
 * @author Jan, Steffen Lortz
 */
public class AbstractJavaClass {
	

	protected DataInputStream bcode;
	
	/** The identifying name of this class */
	protected String className;
	
	/** The identifying name of the direct super class */
	protected String superClassName;
	
	/*
	 * The Data structures to represent the constant pool are of two types: a)
	 * constPoolEntries to represent the regular entries b) Utfs to store the
	 * Utfs_info values. This, each entry is store in either of these.
	 */
	// Stores the information read from the constant pool entries, unless they
	// were Utfs-es
	protected int[] constPoolEntries;
	
	// Maps constant positions to types
	protected int[] types;
	
	// Used to store the Utf8 constants. Their length is unpredictable, so a
	// Vector is used
	protected Vector Utfs;
	
	// Iff cp_info_#X is of type Utfs, then this array indicates at position X
	// where cp_info_#X
	// can be found in the Utfs Vector declared just above
	protected int[] UtfsPos;
	
	// Fields, Methods and Classes may have an unknown number of attributes
	protected Vector attributes;
	
	private static final int CLASS_ENTRY = 7;
	
	private static final int FIELDREF_ENTRY = 9;
	
	private static final int METHODREF_ENTRY = 10;
	
	private static final int INTERFACEMETHODREF_ENTRY = 11;
	
	private static final int STRING_ENTRY = 8;
	
	private static final int INTEGER_ENTRY = 3;
	
	private static final int FLOAT_ENTRY = 4;
	
	private static final int LONG_ENTRY = 5;
	
	private static final int DOUBLE_ENTRY = 6;
	
	private static final int NAME_AND_TYPE_ENTRY = 12;
	
	private static final int UTF8_ENTRY = 1;
	

	/**
	 * Uses the input stream specified to parse a java class. The actual parsing
	 * is only done when readClass is invoked, so the input stream needs to be
	 * at the appropriate position then.
	 * 
	 * @param is A stream representation of a java class
	 */
	public AbstractJavaClass(InputStream is) {
		this.bcode = new DataInputStream(is);
	}
	
	/**
	 * Returns the className of this AbstractJavaClass.
	 * 
	 * @return the className
	 */
	public String getClassName() {
		return this.className;
	}
	
	/**
	 * Returns the superClassName of this AbstractJavaClass.
	 * 
	 * @return the superClassName
	 */
	public String getSuperClassName() {
		return this.superClassName;
	}
	
	/**
	 * Loads an entry form the constant pool
	 * 
	 * @param arg A signed byte to load from an entry in the range of 0...255
	 * @return the entry's value
	 */
	public int getIntegerConstantFromConstantPool(byte arg) {
		return this.constPoolEntries[(arg >= 0) ? arg : 256 + arg];
	}
	
	/**
	 * Loads an entry form the constant pool
	 * 
	 * @param args A signed byte[2] to load from an entry in the range of
	 *            0...256^2-1
	 * @return the entry's value
	 */
	public int getIntegerConstantFromConstantPool(byte[] args) {
		// eliminate the byte's sign before concatenation to obtain the index
		int poolPosition = 256 * ((args[0] >= 0) ? args[0] : 256 + args[0])
				+ ((args[1] >= 0) ? args[1] : 256 + args[1]);
		return this.constPoolEntries[poolPosition];
	}
	
	/**
	 * Parses the input stream provided in the constructor, creating the
	 * necessary structures on the fly. This method should not have to be
	 * overridden. Instead, override the apropriate stage of the parsing
	 * process, which are named to reflect their intent.
	 * 
	 * @return Whether the bytecode has been correctly read.
	 */
	/*
	 * Presently, the file ignores data that does not seem immediately relevant
	 * by dropping it in passing. The reading information are labeled in order
	 * to show which value is read, according to the class definition of the JVM
	 * Specification
	 */
	public boolean readClass() throws IOException, SyntaxException {
		// Comments refer to the elements defined in "The JavaTM Virtual
		// Machine Specification Second Edition"
		this.readMagicMinorMajor(); // magic, minor_version & major_version
		this.readConstantPool(); // constant_pool_count & constant_pool[]
		this.readAccessThisSuper(); // access_flags, this_class & super_class
		this.readInterfaces(); // interface_count and interface[]
		this.readFields(); // fields_count & field[]
		this.readMethods(); // methods_count & methods[]
		this.readClassAttributes(); // attributes_count & attributes[]
		// Helper method for any calculations after parsing
		this.postReadComputations();
		return true;
	}
	
	// Retrieves the value at the corresponding place in the constant table
	protected byte[] get_Utf8_info(int i) {
		return (byte[]) this.Utfs.elementAt(this.UtfsPos[i]);
	}
	
	// needs not do anything, and might not have to for subclasses.
	protected void postReadComputations() {
	}
	
	/* Skips the attribue_info structure. Intended to be overridden. */
	protected void read_class_Attribute_Info() throws IOException {
		this.skip_attribute_Info();
	}
	
	/* Skips the attribue_info structure. Intended to be overridden. */
	protected void read_field_Attribute_Info(int field) throws IOException {
		this.skip_attribute_Info();
	}
	
	/* Skips the attribue_info structure. Intended to be overridden. */
	protected void read_method_Attribute_Info(int method, int descriptor) throws IOException,
			SyntaxException {
		this.skip_attribute_Info();
	}
	
	/* Skips attribute info details */
	protected void readClassAttributes() throws IOException {
		int attributes_count = this.bcode.readShort();
		for (int i = 0; i < attributes_count; i++) {
			this.read_class_Attribute_Info();
		}
	}
	
	protected void readAccessThisSuper() throws IOException {
		this.bcode.readShort(); // access_flags
		int tmp = this.bcode.readShort(); // this_class
		int classInfo = this.constPoolEntries[tmp];
		tmp = classInfo & 0x0000FFFF;
		this.className = new String(this.get_Utf8_info(tmp));
		tmp = this.bcode.readShort(); // super_class
		classInfo = this.constPoolEntries[tmp];
		tmp = classInfo & 0x0000FFFF;
		this.superClassName = new String(this.get_Utf8_info(tmp));
	}
	
	/* Reads the constant pool part of the class */
	protected void readConstantPool() throws IOException, SyntaxException {
		int utfs_elems = 0;
		int constant_pool_count = this.bcode.readShort();
		// Note that "The value of the constant_pool_count item is equal to the
		// number of
		// entries in the constant_pool table plus one [!] according to
		// specification"
		
		this.constPoolEntries = new int[constant_pool_count];
		
		this.types = new int[constant_pool_count];
		// Create a vector to store arbitrary-length Utf8 values
		this.Utfs = new Vector(constant_pool_count);
		this.UtfsPos = new int[constant_pool_count];
		
		// Read the entire constant pool and store the values
		for (int entry = 1; entry < constant_pool_count; entry++) {
			int type = this.bcode.readByte();
			this.types[entry] = type;
			switch (type) {
				case LONG_ENTRY:
				case DOUBLE_ENTRY:
					this.constPoolEntries[entry] = this.bcode.readInt();
					entry++;
					this.constPoolEntries[entry] = this.bcode.readInt();
					break;
				case INTEGER_ENTRY:
				case FLOAT_ENTRY:
				case FIELDREF_ENTRY:
				case METHODREF_ENTRY:
				case INTERFACEMETHODREF_ENTRY:
				case NAME_AND_TYPE_ENTRY:
					this.constPoolEntries[entry] = this.bcode.readInt();
					break;
				case CLASS_ENTRY:
				case STRING_ENTRY:
					this.constPoolEntries[entry] = this.bcode.readShort();
					break;
				case UTF8_ENTRY:
					byte[] b = new byte[this.bcode.readShort()];
					this.bcode.read(b);
					this.Utfs.addElement(b);
					this.UtfsPos[entry] = utfs_elems++;
					break;
				default:
					throw new SyntaxException("Error reading class: constant type not known");
			}
			this.Utfs.trimToSize();
		} // for
	}
	
	protected void readFields() throws IOException {
		int fields_count = this.bcode.readShort();
		for (int i = 0; i < fields_count; i++) {
			this.bcode.readShort(); // access_flags
			int name_index = this.bcode.readShort();
			this.bcode.readShort(); // descriptor_index
			int attributes_count = this.bcode.readShort();
			for (int j = 0; j < attributes_count; j++) {
				this.read_field_Attribute_Info(name_index);
			}
		}
	}
	
	protected void readInterfaces() throws IOException {
		int interface_count = this.bcode.readShort();
		for (int i = 0; i < interface_count; i++) {
			this.bcode.readShort(); // interfaces[interface_count]
		}
	}
	
	protected void readMagicMinorMajor() throws IOException, SyntaxException {
		int read = this.bcode.readInt();
		if (read != 0xCAFEBABE) {
			throw new SyntaxException("Error reading class: CAFEBABE not read as first values");
		}
		
		this.bcode.readInt(); // minor_version, major_version skipped at once
	}
	
	protected void readMethods() throws IOException, SyntaxException {
		int methods_count = this.bcode.readShort();
		for (int i = 0; i < methods_count; i++) {
			this.bcode.readShort(); // access_flags
			int name_index = this.bcode.readShort();
			int descriptor_index = this.bcode.readShort();
			int attributes_count = this.bcode.readShort();
			for (int j = 0; j < attributes_count; j++) {
				this.read_method_Attribute_Info(name_index, descriptor_index);
			}
		}
	}
	
	protected void skip_attribute_Info() throws IOException {
		this.bcode.readShort(); // attribute_name_index
		int attribute_length = this.bcode.readInt();
		for (int l = 0; l < attribute_length; l++) {
			this.bcode.readByte(); // info[attribute_length]
		}
	}
}
